// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

// DEBUG
// #include <Arduino.h>


#include <ioxx_common.hpp>


#ifndef _IOXX_BUTTON_TRIGGER_LONG_PRESS_THRESHOLD

    // 假设按键扫描周期是20 ~ 30ms，则默认的长按阈值是1.0s ~ 1.5s
    #define _IOXX_BUTTON_TRIGGER_LONG_PRESS_THRESHOLD (50)

#endif


namespace ioxx {


    /**
     * @brief 按键输入滤波，并触发点击、长按等事件，默认输入低电平有效
     *
     * @tparam BufferType     用作bit 缓冲区的整数类型，默认至少8bit
     * @tparam TriggerCount   默认为2，表示按键电平必须持续稳定低电平2 个周期，才会触发按键状态切换
     */
    template <typename BufferType = uint_fast8_t, uint8_t TriggerCount = 2>
    class ButtonTrigger {
       public:
        static_assert(TriggerCount < (sizeof(BufferType) * 8));

        static constexpr BufferType TriggerMask = (~static_cast<BufferType>(0)) << TriggerCount;
        // static constexpr BufferType PreMask = static_cast<BufferType>(TriggerMask << 1);

       private:
        BufferType _scan_buffer = _scan_buffer_init_value();

        uint_fast8_t _press_counter = 0;

        // 内部状态
        bool _after_reset = false;   // reset 后，检测到一次release 后置为true
        bool _pressed = false;

        // 按键事件标志，使用者读取事件后自动清零
        bool _button_pressed = false;       // 按键按下
        bool _button_released = false;      // 按键抬起
        bool _button_clicked = false;       // 点击事件，表示按键按下后很快抬起，长按后抬起不会触发点击
        bool _button_long_pressed = false;  // 长按

        /**
         * @brief 根据按键有效电平，返回缓冲区初始值
         *
         * 若按键是低电平有效，则初始值应为0x00，然后在检测到一次release 后才进入正常工作循环；
         *
         * @return constexpr BufferType
         */
        constexpr BufferType _scan_buffer_init_value() {
            return 0;
        }


        /**
         * @brief 检测缓冲区的状态是否达到按键按下的消抖阈值
         *
         * 如果滤波设定是连续2 个低电平有效，则只有当_scan_buffer 的第2 位为1，0, 1 两位都为0 才视为有效，
         * 也就是说，不光要检测到连续低电平，还要在低电平之前有至少一个高电平。
         *
         * @return true   按键已按下
         * @return false  按键状态不稳定或已抬起
         */
        bool _is_pressed() const {
            // BufferType tmp = _scan_buffer | PreMask;
            // return tmp == TriggerMask;  // 低位全为0，高位全为1
            return (_scan_buffer | TriggerMask) == TriggerMask;
        }


        /**
         * @brief 检测缓冲区的状态是否达到按键抬起的消抖阈值
         *
         * @return true   按键已抬起
         * @return false  按键状态不稳定或已按下
         */
        bool _is_released() const {
            // BufferType tmp = _scan_buffer & ~(PreMask);
            // BufferType inverse_mask = static_cast<BufferType>(~TriggerCount);
            // return tmp == inverse_mask;  // 低位全为1，高位全为0
            return (_scan_buffer | TriggerMask) == ~static_cast<BufferType>(0);
        }


       public:
        /**
         * @brief 返回button_pressed 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool pressed() {
            bool v = _button_pressed;
            _button_pressed = false;
            return v;
        }


        /**
         * @brief 返回button_released 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool released() {
            bool v = _button_released;
            _button_released = false;
            return v;
        }


        /**
         * @brief 返回button_clicked 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool clicked() {
            bool v = _button_clicked;
            _button_clicked = false;
            return v;
        }


        /**
         * @brief 返回button_long_pressed 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool long_pressed() {
            bool v = _button_long_pressed;
            _button_long_pressed = false;
            return v;
        }


        // 按键滤波函数，接收电平输入，触发按键事件
        bool feed(bool level) {
            _scan_buffer <<= 1;
            if (level) {
                _scan_buffer += 1;
            }

            if(!_after_reset) {
                if (_is_released()) {
                    _after_reset = true;
                }
                else {
                    return false;
                }
            }

            if (!_pressed) {
                if (_is_pressed()) {  // 连续多次低电平，表示按键确实按下了
                    _pressed = 1;
                    _button_pressed = 1;  // 函数外面的标志位由主程序检查，使用一次后清零，以免重复触发
                }
            }
            else {
                if (_is_released()) {  // 连续多次高电平，表示按键抬起。
                    _pressed = 0;

                    // 如果按下的时间没到长按的阈值，则在按键抬起时发出clicked 信号，否则忽略
                    if (_press_counter < _IOXX_BUTTON_TRIGGER_LONG_PRESS_THRESHOLD) {
                        _button_clicked = 1;
                    }
                    _press_counter = 0;
                    _button_released = 1;
                }
                else {
                    // press_counter 在发出long_pressed 信号后就要停止自增，不然press_counter 溢出后又重新从0 开始自增，long_pressed 信号会重复发出
                    if (_press_counter == _IOXX_BUTTON_TRIGGER_LONG_PRESS_THRESHOLD) {
                        _button_long_pressed = 1;
                    }

                    if (_press_counter > _IOXX_BUTTON_TRIGGER_LONG_PRESS_THRESHOLD) {
                        // 如果press_counter 大于阈值，则停止自增，此时press_counter 的值是BUTTON_LONG_PRESS_THRESHOLD + 1，
                        // 不会重复给long_pressed 置位，但是相应的，BUTTON_LONG_PRESS_THRESHOLD + 1 最大值为255，阈值就不能超过254
                    }
                    else {
                        ++_press_counter;
                    }
                }
            }

            return true;
        }


        /**
         * @brief 完全复位按键滤波器
         * 
         * 相比只复位按键事件，完全复位后需要一段事件才能恢复正常工作循环。
         * 一般情况只复位按键事件就足以处理按键冲突等问题。
         * 
         */
        void reset() {
            _pressed = false;
            _after_reset = false;
            _press_counter = 0;
            _scan_buffer = _scan_buffer_init_value();
            reset_event();
        }


        void reset_event() {
            _button_pressed = false;
            _button_released = false;
            _button_clicked = false;
            _button_long_pressed = false;
        }
    };

}  // namespace ioxx